package org.wheel.quartz.service;

import org.quartz.*;
import org.quartz.impl.JobDetailImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.CollectionUtils;
import org.wheel.base.service.impl.AbstractService;
import org.wheel.quartz.DynamicQuartzJobBean;
import org.wheel.quartz.QuartzProps;
import org.wheel.quartz.domain.QuartzLog;
import org.wheel.quartz.repositories.QuartzLogRepository;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author klaus
 * @date 2021/6/30 华小勇 15:33
 */
@ConditionalOnClass({Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class})
public class QuartzService extends AbstractService<QuartzLogRepository, QuartzLog> implements ApplicationRunner {

    static final String DESCRIPTION = "desc";

    protected Scheduler scheduler;

    private QuartzProps quartzProps;

    static Logger logger = LoggerFactory.getLogger(QuartzService.class);

    /**
     * 增加一个job
     *
     * @param jobClass     任务实现类
     * @param jobName      任务名称
     * @param jobGroupName 任务组名
     * @param jobTime      时间表达式 (这是每隔多少秒为一次任务)
     * @param jobTimes     运行的次数 （<0:表示不限次数）
     * @param jobData      参数
     */
    public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime,
                       int jobTimes, Map<String, Object> jobData) {
        try {

            // 任务名称和组构成任务key
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)
                    .withDescription(String.valueOf(jobData.getOrDefault(DESCRIPTION, ""))).build();
            // 设置job参数
            if (jobData != null && jobData.size() > 0) {
                jobDetail.getJobDataMap().putAll(jobData);
            }

            TriggerBuilder<Trigger> builder = this.getTriggerBuilder(jobName, jobGroupName, jobData);
            SimpleScheduleBuilder sbuilder = SimpleScheduleBuilder.repeatSecondlyForever(1)
                    .withIntervalInSeconds(jobTime);

            if (jobTimes > 0) {

                sbuilder.withRepeatCount(jobTimes);
            }

            Trigger trigger = builder.withSchedule(sbuilder).startNow().build();

            scheduler.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 增加一个job
     *
     * @param jobClass     任务实现类
     * @param jobName      任务名称(建议唯一)
     * @param jobGroupName 任务组名
     * @param cron         时间表达式 （如：0/5 * * * * ? ）
     * @param jobData      参数
     */
    public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String cron,
                       Map<String, Object> jobData) {
        try {
            // 创建jobDetail实例，绑定Job实现类
            // 指明job的名称，所在组的名称，以及绑定job类
            // 任务名称和组构成任务key
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)
                    .withDescription(String.valueOf(jobData.getOrDefault(DESCRIPTION, ""))).build();
            // 设置job参数
            if (jobData != null && jobData.size() > 0) {
                jobDetail.getJobDataMap().putAll(jobData);
            }
            // 定义调度触发规则
            TriggerBuilder<Trigger> builder = this.getTriggerBuilder(jobName, jobGroupName, jobData);

            // 使用cornTrigger规则
            Trigger trigger = builder.withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();
            // 把作业和触发器注册到任务调度中
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    TriggerBuilder<Trigger> getTriggerBuilder(String jobName, String jobGroupName, Map<String, Object> jobData) {

        TriggerBuilder<Trigger> builder = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName);

        builder.endAt((Date) jobData.getOrDefault("endAt", null));
        builder.startAt((Date) jobData.getOrDefault("startAt", DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND)));
        builder.withDescription(String.valueOf(jobData.getOrDefault(DESCRIPTION, "")));

        return builder;
    }

    /**
     * 修改 一个job的 时间表达式
     *
     * @param jobName
     * @param jobGroupName
     * @param cron
     */
    public void updateJob(String jobName, String jobGroupName, String cron, String description) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
            // 重启触发器
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 删除任务一个job
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务组名
     */
    public void deleteJob(String jobName, String jobGroupName) {
        try {
            scheduler.deleteJob(new JobKey(jobName, jobGroupName));
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 暂停一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    public void pauseJob(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 恢复一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    public void resumeJob(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 立即执行一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    public void runAJobNow(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {

        if (CollectionUtils.isEmpty(quartzProps.getDynamicJobs())) {
            return;
        }

        Map<String, QuartzProps.QuartzJob> dynamicJobs = quartzProps.getDynamicJobs();
        for (Map.Entry<String, QuartzProps.QuartzJob> entry : dynamicJobs.entrySet()) {
            String name = entry.getKey();
            QuartzProps.QuartzJob quartzJob = entry.getValue();

            /**
             * key 由service+method组成
             */
            String[] beanAndMethod = name.split("-");
            if (beanAndMethod.length == 2) {
                if (quartzJob.getBean() == null || quartzJob.getMethod() == null) {
                    quartzJob.setBean(beanAndMethod[0]);
                    quartzJob.setMethod(beanAndMethod[1]);
                }
            }
            if (quartzJob.getBean() == null || quartzJob.getMethod() == null) {
                logger.error(String.format("%s bean or method could not be null!", quartzJob.getDesc()));
                continue;
            }
            String groupName = quartzJob.getGroupName();
            /**
             * 判断是否已经添加
             */
            List<?> triggers = scheduler.getTriggersOfJob(JobKey.jobKey(name, groupName));
            if (!CollectionUtils.isEmpty(triggers)) {
                continue;
            }
            quartzJob.setName(name);
            String cronExpress = quartzJob.getCron();
            this.addJob(DynamicQuartzJobBean.class, name, groupName, cronExpress, BeanMap.create(quartzJob));
        }
    }

    /**
     * 异步存储运行日志
     * @param context
     */
    @Async
    public void saveQuartzLog(JobExecutionContext context) {
        JobDetailImpl detail = (JobDetailImpl) context.getJobDetail();
        QuartzLog quartzLog = new QuartzLog();
        quartzLog.setName(detail.getName());
        quartzLog.setGroup(detail.getGroup());
        quartzLog.setStartTime(context.getFireTime());
        quartzLog.setNextTime(context.getNextFireTime());
        quartzLog.setEndTime(new Date(context.getJobRunTime()));
        quartzLog.setRunTime(context.getJobRunTime() - context.getFireTime().getTime());
        quartzLog.setText(context.getResult() == null ? "执行成功!" : context.getResult().toString());
        dao.save(quartzLog);
    }

    /**
     * 运行日志查询
     * @param name
     * @param group
     * @param page
     * @param pageSize
     * @return
     */
    public Page<QuartzLog> findAllByNameAndGroup(String name, String group, Integer page, Integer pageSize) {
        return dao.findQuartzLogsByNameAndGroupOrderByStartTimeDesc(name, group, PageRequest.of(page, pageSize));
    }

    public Page<Map<String, Object>> findAllJobs(Map<String, Object> params, Integer page, Integer pageSize) {
        return dao.findAllJobs(params, PageRequest.of(page, pageSize));
    }

    @Resource
    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    @Resource
    public void setQuartzProps(QuartzProps quartzProps) {
        this.quartzProps = quartzProps;
    }

}
